﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MonoStrategy
{
    public class SynchronizedManager
    {
        private readonly LinkedList<WorkItem> m_WorkItems = new LinkedList<WorkItem>();
        private long m_CurrentCycle;
        private long m_CycleResolution;

        internal SynchronizedManager SyncWith { get; private set; }
        /// <summary>
        /// The next cycle time aligned to <see cref="CycleResolution"/>. If <see cref="CurrentCycle"/>
        /// is already aligned, the next aligned cycle is still returned.
        /// </summary>
        internal long NextAlignedCycle
        {
            get
            {
                return (CurrentCycle / CycleResolution + 1) * CycleResolution;
            }
        }
        /// <summary>
        /// The current discrete time value. By convention, everwhere else you need a discrete game time,
        /// just return this value of the path engine.
        /// </summary>
        internal long CurrentCycle
        {
            get
            {
                if (SyncWith == null)
                    return m_CurrentCycle;
                else
                    return SyncWith.CurrentCycle;
            }

            set
            {
                if (SyncWith != null)
                    throw new InvalidOperationException();

                m_CurrentCycle = value;
            }
        }
        /// <summary>
        /// Directly holds the discretization granularity. All movable speeds can only be multiples of
        /// this value, but be careful, since performance goes down polynomial (?just a guess) with 
        /// increasing resolution.
        /// </summary>
        internal long CycleResolution
        {
            get
            {
                if (SyncWith == null)
                    return m_CycleResolution;
                else
                    return SyncWith.CycleResolution;
            }
        }

        internal bool IsAlignedCycle
        {
            get
            {
                return ((CurrentCycle % CycleResolution) == 0);
            }
        }

        public static int MillisToAlignedCycle(long inMillis)
        {
            return (int)((inMillis / (int)CyclePoint.CYCLE_MILLIS + (int)CyclePoint.CYCLE_MILLIS - 1) / (int)CyclePoint.CYCLE_MILLIS) * (int)CyclePoint.CYCLE_MILLIS;
        }

        private class WorkItem
        {
            internal long ExpirationCycle;
            internal Procedure Handler;
        }

        internal SynchronizedManager(long inInitialCycle, long inCycleResolution)
        {
            CurrentCycle = inInitialCycle;
            m_CycleResolution = inCycleResolution;
        }

        internal SynchronizedManager(SynchronizedManager inSyncWith)
        {
            if(inSyncWith == null)
                throw new ArgumentNullException();

            SyncWith = inSyncWith;
        }

        internal void QueueWorkItem(Procedure inTask)
        {
            QueueWorkItem(0, inTask);
        }

        internal void QueueWorkItem(long inDueTimeMillis, Procedure inTask)
        {
            if (inTask == null)
                throw new ArgumentNullException();

            lock (m_WorkItems)
            {
                LinkedListNode<WorkItem> list = m_WorkItems.First;
                long expirationCycle = (CurrentCycle * (long)CyclePoint.CYCLE_MILLIS + inDueTimeMillis + (long)CyclePoint.CYCLE_MILLIS - 1) / (long)CyclePoint.CYCLE_MILLIS;

                while (list != null)
                {
                    if (list.Value.ExpirationCycle > expirationCycle)
                    {
                        // insert work item
                        m_WorkItems.AddBefore(list, new WorkItem()
                        {
                            ExpirationCycle = expirationCycle,
                            Handler = inTask,
                        });

                        break;
                    }

                    list = list.Next;
                }

                if (list == null)
                {
                    // insert work item
                    m_WorkItems.AddLast(new WorkItem()
                    {
                        ExpirationCycle = expirationCycle,
                        Handler = inTask,
                    });
                }
            }
        }

        internal virtual void ProcessCycle()
        {
            // execute expired work items
            lock (m_WorkItems)
            {
                LinkedListNode<WorkItem> list = m_WorkItems.First;

                while (list != null)
                {
                    LinkedListNode<WorkItem> next = list.Next;

                    if (list.Value.ExpirationCycle <= CurrentCycle)
                    {
                        m_WorkItems.Remove(list.Value);

                        list.Value.Handler();
                    }

                    list = next;
                }
            }
        }
    }
}
